﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Dynamic;

namespace IOFtp
{
    public abstract class FtpFileSystemObject : DynamicObject
    {
        public class PermissionAttributes
        {
            public bool IsDirectory { get; private set; } //this property may not seem strictly pertinent here..

            public class Rights
            {
                public bool Read { get; private set; }
                public bool Write { get; private set; }
                public bool Execute { get; private set; }

                public Rights( string rights )
                {
                    if( rights.Length != 3 )
                        throw new ArgumentException( "Permission string must be 3 chars long." );

                    if( rights.Any( @char => @char != 'r' && @char != 'w' && @char != 'x' && @char != '-' ) )
                        throw new ArgumentException( "Permission string (except first char) must contain only 'r', 'w', 'x' or '-' chars." );

                    this.Read = rights.Contains( 'r' );
                    this.Write = rights.Contains( 'w' );
                    this.Execute = rights.Contains( 'x' );
                }
            }

            public Rights OwnerRights { get; private set; }
            public Rights GroupRights { get; private set; }
            public Rights OtherRights { get; private set; }

            /// <param name="rights">chmod-style rights (ex: drwxrwxrwx)</param>
            public PermissionAttributes( string rights )
            {
                if( rights.Length != 10 )
                    throw new ArgumentException( "Permission string must be 10 chars long." );

                rights = rights.ToLower();
                if( rights.First() != '-' && rights.First() != 'd' )
                    throw new ArgumentException( "Permission's first char must be '-' or 'd'." );

                if( rights.Skip( 1 ).Any( @char => @char != 'r' && @char != 'w' && @char != 'x' && @char != '-' ) )
                    throw new ArgumentException( "Permission string (except first char) must contain only 'r', 'w', 'x' or '-' chars." );

                this.OwnerRights = new Rights( rights.Substring( 1, 3 ) );
                this.GroupRights = new Rights( rights.Substring( 4, 3 ) );
                this.OtherRights = new Rights( rights.Substring( 7, 3 ) );

                this.IsDirectory = String.Compare( rights[ 0 ].ToString(), "d", true ) == 0;
            }
        }

        protected FtpWebRequest Request { get; set; }
        protected WebClient WebClient { get; set; }

        public PermissionAttributes Permissions { get; internal set; }

        //To avoid conversion problems due to unpredictable data format I use a string to represent a Date
        public string LastModified { get; internal set; }

        public string Name { get; private set; }
        public string FullPath { get; private set; }

        public Uri CurrentDirectory { get; set; }
        public NetworkCredential Credentials { get; protected set; }

        public FtpFileSystemObject( Uri requestUri, NetworkCredential credentials )
        {
            this.Credentials = credentials;

            this.CurrentDirectory = requestUri;

            this.Request = (FtpWebRequest)WebRequest.Create( requestUri );
            this.Request.ConnectionGroupName = this.GetHashCode().ToString();
            this.Request.Credentials = credentials;
            this.Request.KeepAlive = true;

            this.WebClient = new WebClient();
            this.WebClient.Credentials = credentials;

            this.Name = this.GetDirectoryName( requestUri );
            this.FullPath = requestUri.AbsoluteUri;
        }

        private string GetDirectoryName( Uri requestUri )
        {
            if( requestUri.AbsolutePath == "/" )
                return requestUri.ToString();

            FileInfo fi = new FileInfo( requestUri.AbsolutePath );
            return fi.Directory.Name;
        }

        public FtpFileSystemObject( string requestUri, NetworkCredential credentials )
            : this( new Uri( requestUri ), credentials ) { }

        public void Rename( string newName )
        {
            var request = (FtpWebRequest)WebRequest.Create( this.FullPath.ToString() );
            request.Method = WebRequestMethods.Ftp.Rename;
            request.RenameTo = newName;

            request.GetResponse();
        }

        protected IEnumerable<string> GetDirectoryListDetails()
        {
            this.Request = (FtpWebRequest)WebRequest.Create( this.CurrentDirectory );
            this.Request.ConnectionGroupName = this.GetHashCode().ToString();
            this.Request.Credentials = this.Credentials;
            this.Request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
            this.Request.KeepAlive = true;

            FtpWebResponse response = (FtpWebResponse)this.Request.GetResponse();
            using( Stream responseStream = response.GetResponseStream() )
            {
                using( StreamReader reader = new StreamReader( responseStream ) )
                {
                    string fileSystemDetails = reader.ReadToEnd();
                    return fileSystemDetails.Split( new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries );
                }
            }
        }

        public override int GetHashCode()
        {
            return this.Credentials.Password.GetHashCode() ^
                   this.Credentials.UserName.GetHashCode() ^
                   this.Credentials.Domain.GetHashCode() ^
                   base.GetHashCode() ^
                   101;
        }

        public abstract void Download( string destination );

        public abstract void Delete();
    }
}